/**
 * transtion动画可能有多个属性，每个属性都有完整的duration等定义
 * 多个定义在getComputedStyle中，以', '分割，例如transitionDuration: "0.3s, 0.5s"
 * 存在多个属性的动画时，结束事件在时间最长的属性动画完成时触发
 * 多个属性时间一致时，仅在第一次触发
 * 事件触发后，将移除ontransitionend事件，防止在其它意外的动画导致触发事件
 * 
 * transtion相关的事件有
 * transitionrun - 开始时触发
 * transitionstart - 有delay时，delay之后触发，否则在transitionrun后立即触发
 * transitioncancel - run之后，end之前就不再过度了时触发，例如:hover触发，动画途中不再:hover了
 * transitionend - 动画结束后触发，各种情况可能执行多遍
 * 
 * 以下面CSS来看一些情况
.tra {
  transition: background-color .5s 1s;
  background-color: lime;
}
.tra:hover {
  background-color: red;
}
 * 1. 鼠标进入，立刻触发transitionrun
 * 2.1 鼠标.5s前移出触发transitioncancel
 * 2.2 鼠标.5s，立刻触发transitionstart
 * 2.3 鼠标.5s后1s前移出触发transitioncancel，此时动画已经transitionstart，则需要变回原样
 * 变回原样也是个动画，回到步骤2.1
 * 3. 鼠标悬浮1s后触发transitionend
 * 4. 鼠标移出回到步骤2.1
 * 
 * 注意
 * transition-delay允许为负数
 * 实际动画时长为transition-delay + transition-duration
 * 动画时长小于等于0则不会触发任何动画事件
 */

import { data, directive } from '../ee-directive'

/** 动画状态
 * @typedef {('stop' | 'begin' | 'end')} status
 */

/** 动画回调，会完整的回调，尚未开始，开始，结束
 * @callback onstatus
 * @param {status} status - 动画状态
 * @param {boolean} appear - true: 入动画 / false: 出动画
 * @param {el} el - 当前DOM元素
 */

/** 动画枚举
 * @typedef {('move' | 'top' | 'right' | 'down' | 'left' | 'fade' | 'visible')} animation
 */

/** binding.value
 * @typedef {object} value
 * @property {animation[] | animation} animation - 要播放的动画
 * @property {boolean} appear - true: 入动画 / false: 出动画
 * @property {onstatus} onstatus - 动画状态变化的回调
 */

/** binding.modifiers
 * @typedef {object} modifiers
 */

/** el.__eet
 * @typedef {object} eetdata
 * @property {number} duration - 动画最长的时间，用于仅触发一次动画结束事件
 */

// 以下注释可以忽略
/** binding
 * @typedef {object} binding
 * @property {value} value
 * @property {animation[] | animation} [arg]
 * @property {modifiers} modifiers - 没有可以删掉
 */
/** el
 * @typedef {HTMLElement | {__eet: data | eetdata}} el
 */

/** 动画结束回调
 * @param {TransitionEvent} e
 */
function ontransitionend(e) {
  /** @type {el} */
  const el = e.target;
  // 父元素设置的ontransitionend，子元素有动画结束也会触发
  if (el != this || e.elapsedTime != el.__eet.duration)
    return;
  // 仅触发一次事件
  el.removeEventListener('transitionend', ontransitionend);
  /** @type {binding} */
  const binding = el.__eet.clone;
  el.style.display = binding.value.appear ? '' : 'none';
  binding.value.onstatus?.('end', binding.value.appear, el);
}
/** 获得动画的数组
 * @param {animation[] | animation} animations
 * @returns {animation[]}
 */
function getAnimationList(animations) {
  if (animations) {
    if (animations.constructor == Array)
      return animations;
    else
      return [animations];
  }
  return [];
}

/** 将动画的时间转换成统一的s为单位
 * @param {string} value - 动画的时间，一般是.3s或者300ms
 */
function getTime(value) {
  let time = parseFloat(value);
  if (value.charAt(value.length - 2) == 'm')
    time /= 1000;
  return time;
}

/**
 * @param {el} el
 * @param {binding} binding
 * @param {boolean} value - true: 入 / false: 出
 */
function begin(el, binding, value) {
  // 计算动画时长
  const style = getComputedStyle(el);
  const duration = style.transitionDuration.split(', ');
  const delay = style.transitionDelay.split(', ');
  let max = 0;
  for (let i = 0; i < duration.length; i++) {
    const du = duration[i];
    const de = delay[i];
    const time = getTime(du) + getTime(de);
    if (time > max)
      max = time;
  }
  // 时长0直接触发用户自定义事件
  if (max == 0) {
    binding.value.onstatus?.('stop', value, el);
    if (value)
      el.classList.add("in");
    else
      el.classList.remove("in");
    binding.value.onstatus?.('begin', value, el);
    el.style.display = value ? '' : 'none';
    binding.value.onstatus?.('end', value, el);
    return;
  }

  el.__eet.duration = max;
  el.addEventListener('transitionend', ontransitionend);

  binding.value.onstatus?.('stop', value, el);
  el.style.display = '';
  setTimeout(() => {
    if (value)
      el.classList.add("in");
    else
      el.classList.remove("in");
    binding.value.onstatus?.('begin', value, el);
  })
}

/** 动画指令 */
export default directive({
  name: 'eet',
  /**
   * @param {el} el
   * @param {binding} binding
   */
  change(el, binding, vnode, prevVnode) {
    el.classList.add('eet');
    const data = el.__eet;
    const value = binding.value.appear;
    /** @type {binding} */
    const prev = data.clone;
    // 添加新的，需要重新播放
    const olda = [...getAnimationList(prev.arg), ...getAnimationList(prev.value?.animation)];
    const curr = [...getAnimationList(binding.arg), ...getAnimationList(binding.value?.animation)];
    // 删除已经没有了的动画
    for (const item of olda)
      if (!curr.includes(item))
        el.classList.remove(item);
    // 添加新的动画
    let hasNew = false;
    for (const item of curr) {
      if (!hasNew && !olda.includes(item))
        hasNew = true;
      // 防止重新渲染组件覆盖
      el.classList.add(item);
    }
    if (hasNew) {
      // 已经展示的则重新播放
      if (value) {
        // 让el先隐藏，下一帧再出现并做动画
        el.classList.remove('in');
        el.style.display = 'none';
        setTimeout(() => begin(el, binding, true))
        return;
      }
    }
    // 有新动画需要重新播放
    if (!!prev.value?.appear == !!value) {
      // 是否需要播放动画的状态没变化则不需要播放动画
      // 防止重新渲染组件覆盖
      el.style.display = binding.value.appear ? '' : 'none';
      return;
    }

    begin(el, binding, value);
  }
})